#include "stdafx.h"
#include "HXDirector.h"
#include "HXLink.h"
#include "HXTalk.h"
#include "HXWindow.h"

//=============================================================================

HXDirector g_director;				// internal message handler

//=============================================================================

//! Constructor.
HXDirector::HXDirector()
: m_frameTime(20),
  m_framesRendered(0),
  m_hThread(0),
  m_bThreadExit(false)
{
	InitializeCriticalSectionAndSpinCount(&m_criticalSection_Director, 0x80000400);
}

//! Destructor
HXDirector::~HXDirector()
{
	DeleteCriticalSection(&m_criticalSection_Director);
}

//! Start internal message handler thread.
bool HXDirector::Run()
{
	m_hThread = CreateThread( 
			             NULL,       // default security attributes
				         0,          // default stack size
						 (LPTHREAD_START_ROUTINE) RunDirectorThread, 
						 this,		 // object pointer
						 0,	         // default creation flags
						 NULL);
	if (m_hThread != 0)
	{
		//SetThreadPriority(m_hThread, THREAD_PRIORITY_ABOVE_NORMAL);
		return true;
	}
	return false;
}

//! Stop internal message handler thread.
void HXDirector::Stop()
{
	EnterCriticalSection(&m_criticalSection_Director);
	m_commandList.clear();
	LeaveCriticalSection(&m_criticalSection_Director);
	WaitExecute(L"clearconfig");
	if (m_hThread)
	{
		m_bThreadExit = true;
		WaitForSingleObject(m_hThread, INFINITE);
		CloseHandle(m_hThread);
		m_bThreadExit = false;
		m_hThread = 0;
	}
}

//! Execute passed command; wait for completion.
void HXDirector::WaitExecute(const wchar_t* command)
{
	AsyncExecute(command);
	EnterCriticalSection(&m_criticalSection_Director);
	while (m_commandList.size())
	{
		LeaveCriticalSection(&m_criticalSection_Director);
		Sleep(GetFrameTime());
		EnterCriticalSection(&m_criticalSection_Director);
	}
	LeaveCriticalSection(&m_criticalSection_Director);
}

//! Execute passed command when possible, but return immediately.
void HXDirector::AsyncExecute(const wchar_t* command)
{
	EnterCriticalSection(&m_criticalSection_Director);
	m_commandList.push_back(wstring(command));
	g_link.SetIsBusy(); // can cause deadlocks if other thread locks multiple synapses at once
	LeaveCriticalSection(&m_criticalSection_Director);
}

//! AsyncExecute with variable argument list.
void HXDirector::AsyncExecuteP(const wchar_t* text, ...)
{
    // Format the input string
    va_list pArgs;
    va_start(pArgs, text);

	wchar_t command[MAX_TEXT_LENGTH];

	StringCchVPrintfW(command, MAX_TEXT_LENGTH-1, text, pArgs);
	
    va_end(pArgs);

    // Ensure that the formatted string is NULL-terminated
    command[MAX_TEXT_LENGTH-1] = L'\0';

	AsyncExecute(command);
}


//! Execute passed command (on the active thread).
void HXDirector::SyncExecute(const wchar_t* command)
{
	wchar_t commandCopy[MAX_TEXT_LENGTH];
	wcscpy_s(commandCopy, MAX_TEXT_LENGTH, command);
	wchar_t* taglet;
	wchar_t *next_token;
	if (taglet = wcstok_s(commandCopy, L" ,\t\n\r", &next_token))
	{
		// to lower case
		int taglength = wcslen(taglet);
		for (int i = 0; i < taglength; i++)
		{
			taglet[i] = towlower(taglet[i]);
		}
		// no-argument commands:
		if (wcscmp(L"kill", taglet) == 0)
		{
			g_window.ShowStatus(L"Handling KILL command");
			PostMessage(g_window.GetMainHandle(), WM_CLOSE, 0, 0);
		}
		else
		if (wcscmp(L"clearconfig", taglet) == 0)
		{
			if (g_speechtts.IsLocked())
			{
				 DelayExecute(command);
			}
			else
			{
				g_window.ShowStatus(L"Handling CLEARCONFIG command");
				ConfigClear();
			}
		}
		else
		if (wcscmp(L"clearmodels", taglet) == 0)
		{
			if (g_speechtts.IsLocked())
			{
				DelayExecute(command);
			}
			else
			{
				g_window.ShowStatus(L"Handling CLEARMODELS command");
				ModelClear();
			}
		}
		else
		if (wcscmp(L"clearbg", taglet) == 0)
		{
			g_window.ShowStatus(L"Handling CLEARBG command");
			BackgroundClear();
		}
		else
		{
			// commands with argument(s)
			size_t i = 0;
			while (next_token[i])
			{
				if ((next_token[i] != ' ') &&
					(next_token[i] != ',') &&
					(next_token[i] != '\t') &&
					(next_token[i] != '\n') &&
					(next_token[i] != '\r'))
				{
					break;
				}
				i++;
			}
			wstring warg = wstring(next_token+i);
			if ((wcscmp(L"em", taglet) == 0) || (wcscmp(L"emote", taglet) == 0))
			{
				g_talk.ParseTag(command, true); // play emote now
			}
			else
			if (wcscmp(L"quick", taglet) == 0)
			{
				int id = _wtoi(next_token+i);
				if ((id >= 0) && (id < 10))
				{
					PostMessage(g_window.GetMainHandle(), WM_COMMAND, IDM_QUICKACTION_0 + id, 0);
				}
			}
			else
			if (wcscmp(L"setpath", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling SETPATH command with argument: %s", warg.c_str());
				g_paths.SetWorkingPath(warg);
			}
			else
			if (wcscmp(L"loadconfig", taglet) == 0)
			{
				if (g_speechtts.IsLocked())
				{
					DelayExecute(command);
				}
				else
				{
					g_window.ShowStatus(L"Handling LOADCONFIG command with argument: %s", warg.c_str());
					ConfigOpen(warg, true);
				}
			}
			else
			if (wcscmp(L"config", taglet) == 0)
			{
				if (g_speechtts.IsLocked())
				{
					DelayExecute(command);
				}
				else
				{
					g_window.ShowStatus(L"Handling CONFIG command with argument: %s", warg.c_str());
					wstring wxml(L"<headx> ");
					wxml.append(warg);
					wxml.append(L" </headx>");
					ConfigOpen(wxml, false);
				}
			}
			else
			if (wcscmp(L"loadparam", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling LOADPARAM command with argument: %s", warg.c_str());
				ParamOpen(warg, true);
			}
			else
			if (wcscmp(L"param", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling PARAM command with argument: %s", warg.c_str());
				wstring wxml(L"<headx> ");
				wxml.append(warg);
				wxml.append(L" </headx>");
				ParamOpen(wxml, false);
			}
			else
			if (wcscmp(L"loadmodel", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling LOADMODEL command with argument: %s", warg.c_str());
				ModelAdd(warg, true);
			}
			else
			if (wcscmp(L"model", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling MODEL command with argument: %s", warg.c_str());
				wstring wxml(L"<headx> ");
				wxml.append(warg);
				wxml.append(L" </headx>");
				ModelAdd(wxml, false);
			}
			else
			if (wcscmp(L"removemodel", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling REMOVEMODEL command with argument: %s", warg.c_str());
				//int index = atoi(WStringToString(warg).c_str());
				ModelRemove(warg);
			}
			else
			if (wcscmp(L"exportmodels", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling EXPORTMODELS command with argument: %s", warg.c_str());
				ModelExport(warg);
			}
			else
			if (wcscmp(L"loadbg", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling LOADBG command with argument: %s", warg.c_str());
				BackgroundOpen(warg);
			}
			else
			if (wcscmp(L"sound", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling SOUND command with argument: %s", warg.c_str());
				PlaySound(warg.c_str(), NULL, SND_FILENAME | SND_ASYNC);
			}
			else
			if (wcscmp(L"transparent", taglet) == 0)
			{
				g_window.ShowStatus(L"Handling TRANSPARENT command with argument: %s", warg.c_str());
				SwitchDisplay(WStringToBool(warg));
			}
			else
			{
				g_window.ShowStatus(L"Unknown command: %s %s", taglet, warg.c_str());
			}
		}
	}
}

//! Delay command execution until after speech.
void HXDirector::DelayExecute(const wchar_t* command)
{
	HXAliveCommand* pCommand = new HXAliveCommand(wstring(command));
	pCommand->TriggeredStart(MAX_WORDINDEX);
	g_alive.Add(pCommand);
}

//***********************

//! Change the size of the OpenGL display.
void HXDirector::SetOGLSize(int cx, int cy)
{
	m_3Display.SetSize(cx,cy);
}

//! Rotate the scene.
void HXDirector::Rotate(float x, float y, float z)
{
	m_3Display.Rotate(g_config.resourceGroup, x, y, z);
	m_3Display.CameraStatus();
}

//! Scale the view.
void HXDirector::Scale(float x, float y, float z)
{
	//float em = exp(m);
	m_3Display.Scale(g_config.resourceGroup, x, y, z);
	m_3Display.CameraStatus();
}

//! Translate the view.
void HXDirector::Translate(float x, float y, float z)
{
	m_3Display.Translate(g_config.resourceGroup, x, y, z);
	m_3Display.CameraStatus();
}

//! Reset the head rotation.
void HXDirector::ResetRotation() 
{ 
	m_3Display.ResetRotation(g_config.resourceGroup);
	m_3Display.CameraStatus();
}

//! Reset the head scale.
void HXDirector::ResetScale() 
{ 
	m_3Display.ResetScale(g_config.resourceGroup);
	m_3Display.CameraStatus();
}

//! Reset the head translation.
void HXDirector::ResetTranslation() 
{ 
	m_3Display.ResetTranslation(g_config.resourceGroup);
	m_3Display.CameraStatus();
}

//! Change the light direction.
void HXDirector::RotateLight(size_t lightId, float azimuth, float elevation)
{
	m_3Display.ChangeLight(lightId, azimuth, elevation, 0.0f, 0.0f);
	m_3Display.LightStatus();
}

//! Change the light brightness.
void HXDirector::LightIntensity(size_t lightId, float ambient, float diffuse)
{
	m_3Display.ChangeLight(lightId, 0.0f, 0.0f, ambient, diffuse);
	m_3Display.LightStatus();
}

//! Reset the lights to internal defaults.
void HXDirector::ResetLights(bool loadFromConfig)
{
	m_3Display.ResetLights(loadFromConfig);
	m_3Display.LightStatus();
}

//! Change the position of the subtitle.
void HXDirector::MoveSubtitlePosition(float x, float y, float z)
{
	m_3Display.MoveSubtitlePosition(x, y, z);
	m_3Display.SubtitleStatus();
}

//! Reset the position of the subtitle to internal defaults.
void HXDirector::ResetSubtitlePosition()
{
	m_3Display.ResetSubtitlePosition();
	m_3Display.SubtitleStatus();
}

//! Morph into and out of the specified morph target (for testing purposes).
void HXDirector::PlayMorph(const wstring& label, float duration, float intensity)
{
	HXStopWatch timer;
	float absolute_intensity = 0;
	float last_intensity = 0;
	float intensity_change = 0;
	timer.startTimer();
	timer.stopTimer();
	while (timer.getElapsedTime() < duration)
	{
		if (g_main.doAuxExit)
		{
			m_3Display.ChangeMorphValue(HXMS_OTHER, label, -absolute_intensity);
			return;
		}
		last_intensity = absolute_intensity;
		absolute_intensity = intensity * (timer.getElapsedTimeF()/duration > 0.5 ? 2 * (1-timer.getElapsedTimeF()/duration) : 2 * timer.getElapsedTimeF()/duration);
		intensity_change = absolute_intensity - last_intensity;
		m_3Display.ChangeMorphValue(HXMS_OTHER, label, intensity_change);
		timer.stopTimer();
	}
	m_3Display.ChangeMorphValue(HXMS_OTHER, label, -last_intensity-intensity_change);
}

//! Switch between normal window and transparent window modes.
void HXDirector::SwitchDisplay(bool bLayered)
{
	//m_3Display.UnloadFont();
	if (bLayered)
	{
		if (!m_3Display.IsLayeredDisplay())
		{
			RECT r;
			GetWindowRect(g_window.GetMainHandle(), &r);
			ShowWindow(g_window.GetMainHandle(), SW_HIDE);
			ShowWindow(g_window.GetLayerHandle(), SW_SHOW);
			if (m_3Display.SwitchDisplay(g_window.GetLayerHandle(), true))
			{
				SetWindowPos(g_window.GetLayerHandle(), 0, r.left, r.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);		
			}
			else
			{
				ShowWindow(g_window.GetLayerHandle(), SW_HIDE);
				ShowWindow(g_window.GetMainHandle(), SW_SHOW);
				g_config.bWindowTransparent = false; // to remove the tick next to Transparent...
				g_window.UpdateViewMenu();
				g_window.ShowStatus(L"OpenGL transparent display creation failed, returning to windowed view...");
			}
		}
		else
		{
			g_window.ShowStatus(L"OpenGL display already transparent");
		}
	}
	else
	{
		if (m_3Display.IsLayeredDisplay())
		{
			ShowWindow(g_window.GetLayerHandle(), SW_HIDE);
			ShowWindow(g_window.GetMainHandle(), SW_SHOW);
			if (!m_3Display.SwitchDisplay(g_window.GetMainHandle(), false))
			{
				g_window.ShowErrorStatus(L"OpenGL display creation failed");
			}
		}
		else
		{
			g_window.ShowStatus(L"OpenGL display already in windowed view");
		}
	}
	//m_3Display.LoadFont(g_config.subtitleFont, static_cast<unsigned int>(g_config.subtitleFontSize));
	m_3Display.InitFont(static_cast<unsigned int>(g_config.subtitleFontSize));
}

//***********************

//! Start internal message handling thread.
DWORD WINAPI HXDirector::RunDirectorThread(LPVOID lpParam) 
{ 
	return ((HXDirector*)lpParam)->DirectorThread();
}

//! Internal message handling thread.
DWORD WINAPI HXDirector::DirectorThread()
{
	if (!m_3Display.CreateDisplay(g_window.GetMainHandle(), false))
	{
		g_window.ShowErrorStatus(L"OpenGL display creation failed");
	}
	while (!m_bThreadExit) 
	{
		m_threadClock.startTimer();
		EnterCriticalSection(&m_criticalSection_Director);
		while (m_commandList.size())
		{
			wstring currentCommand = m_commandList.front();
			LeaveCriticalSection(&m_criticalSection_Director);
			g_link.SetIsBusy();
			SyncExecute(currentCommand.c_str());
			EnterCriticalSection(&m_criticalSection_Director);
			m_commandList.pop_front();
		}
		g_link.SetIsReady();
		LeaveCriticalSection(&m_criticalSection_Director);
		g_alive.Callback();
		g_subtitle.Generate();
		//m_3Display.UpdateBackground();
		m_3Display.UpdateMorphs();
		m_3Display.Paint();
		m_threadClock.stopTimer();
		int processTime = static_cast<int>(m_threadClock.getElapsedTime() * 1000);
		m_framesRendered++;
		if (processTime < GetFrameTime())
		{
			Sleep(GetFrameTime() - processTime);
		}
	}
	m_3Display.DestroyDisplay();
	return 0;
}

//***********************

//! Loads a Head X configuration file from the specified path.
bool HXDirector::ConfigOpen(wstring& argument, bool isFilename)
{
	// clean up first
	m_3Display.Lock();
	m_3Display.UnloadAllModels(g_config.resourceGroup);
	m_3Display.UnloadFont();
	m_3Display.Unlock();
	g_window.UpdateModelsMenu();

	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (isFilename)
	{
		wstring fileDir, fileName;
		if (g_paths.SplitFilePath(argument, &fileDir, &fileName))
		{

			g_paths.SetWorkingPath(fileDir);
			g_paths.SetLast(fileDir, fileName);

			if (g_config.LoadFromXML(fileName, isFilename))
			{
				goto config_init;
			}
		}
		g_window.ShowErrorStatus(L"Configuration file not found: %s", argument.c_str());
		g_link.SetLastError(HXSY_ERROR_LOAD);
		CoUninitialize();
		return false;
	}
	else
	{
		if (g_config.LoadFromXML(argument, isFilename))
		{
			goto config_init;
		}
		g_window.ShowErrorStatus(L"Configuration string not valid: %s", argument.c_str());
		g_link.SetLastError(HXSY_ERROR_PARSE);
		CoUninitialize();
		return false;
	}

config_init:
	CoUninitialize();
	g_logger.Open(g_config.logFileName);
	m_3Display.Lock();
	m_3Display.LoadFromConfig(g_config);
	m_3Display.ResetDisplay();
	m_3Display.Unlock();
	g_window.UpdateModelsMenu();
	g_window.ApplyCurrentConfig();
	g_link.SetLastError(HXSY_NONE);
	return true;
}

//! Loads a Head X configuration parameters (no models) from the specified path.
bool HXDirector::ParamOpen(wstring& argument, bool isFilename)
{
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (isFilename)
	{
		wstring fileDir, fileName;
		if (g_paths.SplitFilePath(argument, &fileDir, &fileName))
		{
			g_paths.SetWorkingPath(fileDir);
			g_paths.SetLast(fileDir, fileName);
			if (g_config.LoadFromXML(fileName, isFilename, true))
			{
				goto param_init;
			}
		}
		g_link.SetLastError(HXSY_ERROR_LOAD);
		CoUninitialize();
		return false;
	}
	else
	{
		if (g_config.LoadFromXML(argument, isFilename, true))
		{
			goto param_init;
		}
		g_link.SetLastError(HXSY_ERROR_PARSE);
		CoUninitialize();
		return false;
	}

param_init:
	CoUninitialize();
	//g_alive.Clear();
	g_window.ApplyCurrentConfig();
	g_link.SetLastError(HXSY_NONE);
	return true;
}

//! Clear any previously loaded configuration.
void HXDirector::ConfigClear()
{
	if (g_alive.Idle())
		g_window.ClickSetIdle();
	m_3Display.Lock();
	m_3Display.UnloadAllModels(g_config.resourceGroup);
	m_3Display.UnloadFont();
	m_3Display.UnloadBackground();
	g_logger.Close();
	g_config.Clear();
	m_3Display.Unlock();
	g_window.UpdateModelsMenu();
	g_link.SetLastError(HXSY_NONE);
}

//! Load a background image.
bool HXDirector::BackgroundOpen(wstring& filePath)
{
	wstring fileDir;
	bool success = false;
	if (g_paths.SplitFilePath(filePath, &fileDir, NULL))
	{
		g_paths.SetWorkingPath(fileDir);
		success = m_3Display.LoadBackground(filePath);
		if (success)
		{
			m_3Display.BuildBackground();
			g_link.SetLastError(HXSY_NONE);
		}
	}
	if (!success) g_link.SetLastError(HXSY_ERROR_LOAD);
	return success;
}

//! Removes the background image.
void HXDirector::BackgroundClear()
{
	m_3Display.UnloadBackground();
	g_link.SetLastError(HXSY_NONE);
}

//! Add a 3D model from the specified configuration file.
bool HXDirector::ModelAdd(wstring& argument, bool isFilename)
{
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (isFilename)
	{
		wstring fileDir;
		if (g_paths.SplitFilePath(argument, &fileDir, NULL))
		{
			g_paths.SetWorkingPath(fileDir);
			if (g_config.AddModelsFromXML(argument, isFilename))
			{
				goto acc_init;
			}
		}
		g_link.SetLastError(HXSY_ERROR_LOAD);
		CoUninitialize();
		return false;
	}
	else
	{
		if (g_config.AddModelsFromXML(argument, isFilename))
		{
			goto acc_init;	
		}
		g_link.SetLastError(HXSY_ERROR_PARSE);
		CoUninitialize();
		return false;
	}

acc_init:
	CoUninitialize();
	m_3Display.LoadFromConfig(g_config);
	m_3Display.Lock();
	m_3Display.BuildNewRenderData();
	m_3Display.BuildBackground();
	m_3Display.Unlock();
	g_window.UpdateModelsMenu();
	g_link.SetLastError(HXSY_NONE);
	return true;
}

//! Clear all 3D models.
void HXDirector::ModelClear()
{
	if (m_3Display.ModelSize())
	{
		m_3Display.UnloadAllModels(g_config.resourceGroup);
		g_window.UpdateModelsMenu();
	}
	g_link.SetLastError(HXSY_NONE);
}

//! Remove the specified 3D model.
void HXDirector::ModelRemove(wstring& argument)
{
	//m_3Display.UnloadSingleModel(id, g_config.resourceGroup);
	m_3Display.UnloadModelGroup(argument, g_config.resourceGroup);
	g_window.UpdateModelsMenu();
	g_link.SetLastError(HXSY_NONE);
}

//! Export all models to file.
bool HXDirector::ModelExport(wstring& filePath)
{
	wstring fileDir;
	if (g_paths.SplitFilePath(filePath, &fileDir, NULL, true))
	{
		g_paths.SetWorkingPath(fileDir);
		if (m_3Display.ExportModels(filePath))
		{
			g_link.SetLastError(HXSY_NONE);
			return true;
		}
	}
	g_link.SetLastError(HXSY_ERROR_SAVE);
	return false;
}

//-----------------------------------------------------------------------------